import { ShaderMaterial, IShaderMaterialOptions } from '@babylonjs/core/Materials/shaderMaterial';
import { Color3 } from '@babylonjs/core/Maths/math.color';
import { Scene } from '@babylonjs/core/scene';
import { Texture } from '@babylonjs/core/Materials/Textures/texture';


/**
 * 硬角线材质参数对象
 * @param width 线宽
 * @param sizeAttenuation 是否保持视觉宽度
 * @param depthOffset 深度偏移
 * @param dashSize 虚线段长度
 * @param gapSize 虚线空白长度
 * @param opacity 不透明度
 * @param dashed 是否为虚线
 * @param diffuseColor 颜色
 * @param diffuseTexture 纹理
 * @param perspectiveFactor 透视矫正因数（用于投影模式切换时控制最佳粗细，一般不需要设置）
 */
interface IHardLineMaterialOptions extends IShaderMaterialOptions {
    width?: number,
    sizeAttenuation?: boolean,
    depthOffset?: number,
    dashSize?: number,
    gapSize?: number,
    opacity?: number,
    dashed?: boolean,
    diffuseColor?: Color3,
    diffuseTexture?: Texture,
    perspectiveFactor?: number,
}


/**
 * 硬角线材质
 * @param name 名称
 * @param scene 所在场景
 * @param options 参数对象
 */
class HardLineMaterial extends ShaderMaterial {

    private _width!: number;
    private _perspectiveFactor!: number;
    private _sizeAttenuation!: boolean;
    private _depthOffset!: number;
    private _dashSize!: number;
    private _gapSize!: number;
    private _opacity!: number;
    private _dashed!: boolean;
    private _diffuseColor!: Color3;
    private _diffuseTexture!: Texture | null;

    constructor(name: string, scene: Scene, options?: Partial<IHardLineMaterialOptions>) {
        super(name, scene, '', options);
        this.initialize(options);
    }

    /**
     * 初始化变量、着色器，设置uniform值
     * @param options 参数对象 
     */
    private initialize(options?: Partial<IHardLineMaterialOptions>): void {
        this.initializeVariable(options);
        this.initializeShader();
        this.initializeUniforms();
        this.fillMode = ShaderMaterial.TriangleFillMode;
        this.doNotSerialize = true;
        this.cullBackFaces = false;
    }

    /**
     * 初始化变量
     * @param options 参数对象
     */
    protected initializeVariable(options?: Partial<IHardLineMaterialOptions>): void {
        const op = options || {};
        this._width = op.width || 0.1;
        this._perspectiveFactor = op.perspectiveFactor || 1;
        this._sizeAttenuation = !!op.sizeAttenuation;
        this._depthOffset = op.depthOffset || 0;
        this._dashSize = op.dashSize || 0.1;
        this._gapSize = op.gapSize || 0.1;
        this._opacity = op.opacity || 1;
        this._dashed = !!op.dashed;
        this._diffuseColor = op.diffuseColor || new Color3(1, 1, 1);
        this._diffuseTexture = op.diffuseTexture || null;
    }

    /**
     * 设置是否为虚线
     */
    public set dashed(dashed: boolean) {
        this._dashed = dashed;
        this.setInt('dashed', dashed ? 1 : 0);
    }

    /**
     * 获取是否为虚线
     */
    public get dashed(): boolean {
        return this._dashed;
    }

    /**
     * 设置不透明度
     */
    public set opacity(opacity: number) {
        this._opacity = opacity;
        this.setFloat('opacity', opacity);
    }

    /**
     * 获取不透明度
     */
    public get opacity(): number {
        return this._opacity;
    }

    /**
     * 设置虚线空白长度
     */
    public set gapSize(size: number) {
        this._gapSize = size;
        this.setFloat('gapSize', size);
    }

    /**
     * 获取虚线空白长度
     */
    public get gapSize(): number {
        return this._gapSize;
    }

    /**
     * 设置虚线段长度
     */
    public set dashSize(size: number) {
        this._dashSize = size;
        this.setFloat('dashSize', size);
    }

    /**
     * 获取虚线段长度
     */
    public get dashSize(): number {
        return this._dashSize;
    }

    /**
     * 设置深度偏移
     */
    public set depthOffset(offset: number) {
        this._depthOffset = offset;
        this.setFloat('depthOffset', offset);
    }

    /**
     * 获取深度偏移
     */
    public get depthOffset(): number {
        return this._depthOffset;
    }

    /**
     * 设置是否保持视觉线宽
     */
    public set sizeAttenuation(attenuation: boolean) {
        this._sizeAttenuation = attenuation;
        this.setInt('sizeAttenuation', attenuation ? 1 : 0);
    }

    /**
     * 获取是否保持视觉线宽
     */
    public get sizeAttenuation(): boolean {
        return this._sizeAttenuation;
    }

    /**
     * 设置透视矫正因数
     */
    public set perspectiveFactor(factor: number) {
        this._perspectiveFactor = factor;
        this.setFloat('perspectiveFactor', factor);
    }

    /**
     * 获取透视矫正因数
     */
    public get perspectiveFactor(): number {
        return this._perspectiveFactor;
    }

    /**
     * 设置线宽
     */
    public set width(width: number) {
        this._width = width;
        this.setFloat('width', width);
    }

    /**
     * 获取线宽
     */
    public get width(): number {
        return this._width;
    }

    /**
     * 设置颜色
     */
    public set diffuseColor(color: Color3) {
        this._diffuseColor = color;
        this.setColor3('diffuseColor', color);
    }

    /**
     * 获取颜色
     */
    public get diffuseColor(): Color3 {
        return this._diffuseColor;
    }

    /**
     * 设置纹理
     */
    public set diffuseTexture(texture: Texture | null) {
        const change = (!!texture) !== (!!this._diffuseTexture)
        this._diffuseTexture = texture;
        if (change) {
            if (texture) {
                this.options.defines.push('USE_DIFFUSE_TEXTURE');
                this.options.samplers.push('diffuseTexture');
                this.setTexture('diffuseTexture', texture);
            } else {
                this.options.defines.splice(this.options.defines.indexOf('USE_DIFFUSE_TEXTURE'), 1);
                this.options.samplers.splice(this.options.samplers.indexOf('diffuseTexture'), 1);
            }
        } else {
            if (texture) this.setTexture('diffuseTexture', texture);
        }
    }

    /**
     * 获取纹理
     */
    public get diffuseTexture(): Texture | null {
        return this._diffuseTexture;
    }

    /**
     * 设置uniform值
     */
    protected initializeUniforms(): void {
        this.options.attributes = ['position', 'nextPosition', 'offset', 'lineDistance', 'dashMark'];
        this.options.uniforms = ['worldView', 'projection',
            'width', 'perspectiveFactor', 'sizeAttenuation', 'depthOffset', 'dashSize', 'gapSize', 'opacity', 'dashed', 'diffuseColor'];

        if (this._diffuseTexture) {
            this.options.defines.push('USE_DIFFUSE_TEXTURE');
            this.options.samplers.push('diffuseTexture');
            this.setTexture('diffuseTexture', this._diffuseTexture);
        }

        this.setFloat('width', this._width);
        this.setFloat('perspectiveFactor', this._perspectiveFactor);
        this.setInt('sizeAttenuation', this._sizeAttenuation ? 1 : 0);
        this.setFloat('depthOffset', this._depthOffset);
        this.setFloat('dashSize', this._dashSize);
        this.setFloat('gapSize', this._gapSize);
        this.setFloat('opacity', this._opacity);
        this.setInt('dashed', this._dashed ? 1 : 0);
        this.setColor3('diffuseColor', this._diffuseColor);

    }

    /**
     * 初始化顶点着色器和片元着色器
     */
    protected initializeShader(): void {
        this.shaderPath = {
            vertexSource: `
                precision highp float;
                attribute vec3 position;
                attribute vec3 nextPosition;
                attribute float offset;
                attribute float lineDistance;
                attribute float dashMark;
                uniform mat4 worldView;
                uniform mat4 projection;
                uniform float width;
                uniform float perspectiveFactor;
                uniform int sizeAttenuation;
                uniform float depthOffset;
                varying float vDistance;
                varying float vDashMark;
                varying vec2 vuv;
                void main(){
                    vec4 mvPosition=worldView*vec4(position,1.0);
                    vec4 pmvPosition=projection*mvPosition;

                    vec4 mvNextPosition=worldView*vec4(nextPosition,1.0);
                    vec4 nxtP=projection*mvNextPosition;

                    vec4 curP=pmvPosition;
                    vec3 nxt=vec3(nxtP.xyz/nxtP.w);
                    vec3 cur=vec3(curP.xyz/curP.w);
                    vec3 dirOri=nxt-cur;
                    vec3 look=vec3(0.0,0.0,1.0);

                    vec3 dirCC=mvPosition.xyz/mvPosition.w;
                    vec3 dirCN=mvNextPosition.xyz/mvNextPosition.w;
                    float dotC=dot(look,dirCC);
                    float dotN=dot(look,dirCN);
                    if(dotC*dotN<0.){ 
                        if(projection[3][3]==0.0){ 
                            look=vec3(0.0,0.0,-1.0);
                        }
                    }

                    float aspect=projection[0][0]/projection[1][1];
                    dirOri.y*=aspect;
                    vec3 crs=cross(look,dirOri);
                    vec3 dir=normalize(crs);
                    dir.x*=aspect;
                    float rad=width;
                    pmvPosition.z+=depthOffset;
                    if(sizeAttenuation==1){ 
                        gl_Position=vec4(vec3(pmvPosition.xyz)+dir*rad/perspectiveFactor*offset*pmvPosition.w,pmvPosition.w);
                    }else{ 
                        rad*=projection[1][1];
                        gl_Position=vec4(vec3(pmvPosition.xyz)+dir*rad/perspectiveFactor*offset,pmvPosition.w);
                    }
                    vDistance=lineDistance;
                    vDashMark=dashMark;
                    vuv=vec2(lineDistance,sign(offset)*0.5);
            
                }
            `,
            fragmentSource: `
                precision highp float;
                uniform float dashSize; 
                uniform float gapSize;
                uniform float opacity; 
                uniform int dashed;
                uniform vec3 diffuseColor;
                #ifdef USE_DIFFUSE_TEXTURE
                    uniform sampler2D diffuseTexture;
                #endif
                varying float vDistance;
                varying float vDashMark;
                varying vec2 vuv;
                void main(){
                    vec4 tsl=vec4(1.);
                    #ifdef USE_DIFFUSE_TEXTURE
                        tsl=texture(diffuseTexture,vec2(vuv.x,vuv.y+0.5));
                    #endif
                    vec4 col=vec4(diffuseColor.r*tsl.r,diffuseColor.g*tsl.g,diffuseColor.b*tsl.b,tsl.a*opacity);
                    if(dashed==0||vDashMark==0.){
                        gl_FragColor=col;
                    }else{
                        float dev=mod(vDistance,dashSize+gapSize);
                        if(dev>dashSize){
                            discard;
                        }
                        gl_FragColor=col;
                    }
                }
            `,
        };
    }

}


export { HardLineMaterial };
export type { IHardLineMaterialOptions };